# Copyright (c) 2009 Stefan van der Haven
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""This module contains the classes representing the method groups of the
Hyves API.

The API methods are organized in groups. The methods have a 
"[group].[method]" naming scheme. For instance, the method for searching users
is named "users.search".

Since this resembles Python member functions, it might be a good idea to
create a separate class for each group of API methods.

Coding each class by hand is pretty cumbersome and error-prone, so only the
definitions (ie. member function name, name of the API method it calls,
function arguments, docstring) are coded in this module. When the
hyves.methodgroups module is imported, the class for each group is dynamically
generated as a subclass of MethodGroup, and is made a member of this module.
Also, each class is put in the METHOD_GROUPS dictionary, using the class name
as key.
"""

# _METHOD_DEFS defines the class methods of the various method group classes as
# follows:
#
# {
#     method_group_name: {
#         class_method_name:
#             (
#                 API_method_name_after_period, (
#                     (arg1_name, conversion_function_name),
#                     (arg2_name, conversion_function_name),
#                     ...,
#                     (optional_arg1_name, conversion_function_name, default1),
#                     (optional_arg2_name, conversion_function_name, default1),
#                     ...,
#                 ),
#                docstring,
#             )
#         ...
#     },
#     ...
# }

def iterable_to_csv(arg):
    if isinstance(arg, basestring):
        return arg
    return ','.join(arg)

_PAGINATION_ARGS = (
    ('ha_page', '', None),
    ('ha_resultsperpage', '', None),
)

_METHOD_DEFS = {
    'users' : {
        'get':
         (None,
          (
           ('userid', 'iterable_to_csv'),
           ('ha_responsefields', 'iterable_to_csv', None),
          ),
          'Retrieves basic information for one or more users based on user ID'
         ),
         
        'getByUsername':
         (None,
          (
           ('username','iterable_to_csv'),
           ('ha_responsefields', 'iterable_to_csv', None),
          ),
          'Retrieves basic information for one or more users based on user name'
         ),
         
        'getByGroupLastlogin':
         (None,
          (
           ('groupid',''),
           ('ha_responsefields', 'iterable_to_csv', None),
          ) + _PAGINATION_ARGS,
          'Retrieves users for any group, limited to 150 users sorted by last login.'
         )
    },
}

class MethodGroup(object):
    
    def __init__(self, consumer):
        self._consumer = consumer


_CLASS_METHOD_TEMPLATE = """
def %(method_name)s(%(method_args)s):
    %(method_doc)s
    _request_data = {
        %(required_args)s
    }
    %(optional_args)s
    return self._consumer.make_call(
        %(api_method)r, **_request_data
    )
"""

def _get_class_methods(group_name, method_defs):
    code_str = []
    for class_method_name, method_def in method_defs.iteritems():
        api_method, args, docstring = method_def
        if not api_method:
            api_method = class_method_name
        ha_method = '%s.%s' % (group_name, api_method)
        method_args = ['self']
        required_args = []
        optional_args = []
        for arg in args:
            arg_name, arg_conv = arg[:2]
            if arg_conv:
                arg_get_value = '%s(%s)' % (arg_conv, arg_name)
            else:
                arg_get_value = arg_name
            # if the arg tuple has a third element, it denotes an optional
            # argument
            if len(arg) < 3: 
                method_args.append(arg_name)
                required_args.append('%r: %s' % (arg_name, arg_get_value))
            else:
                method_args.append('%s=%s' % (arg_name, repr(arg[2])))
                optional_args.append(
                    'if %s is not None:\n        _request_data[%r] = %s' %
                    (arg_name, arg_name, arg_get_value)
                )

        if docstring:
            docstring = '"""%s\n    """' % docstring
        code_str.append(_CLASS_METHOD_TEMPLATE % {
            'method_name':class_method_name, 
            'method_args':', '.join(method_args),
            'required_args':'\n        '.join(required_args),
            'optional_args':'\n    '.join(optional_args),
            'method_doc':docstring,
            'api_method':ha_method,
        })
    code_str = ''.join(code_str)
    code = compile(code_str, '<methods for %s>' % group_name, 'exec')
    exec_env = dict(globals())
    exec code in exec_env
    return dict([(name, exec_env[name]) for name in method_defs])

def MethodGroupClass(group_name, methods):
    return type(
        group_name, (MethodGroup,), methods
    )

METHOD_GROUPS = {}
__all__ = ['MethodGroup', 'METHOD_GROUPS']

for group_name, methods in _METHOD_DEFS.iteritems():
    locals()[group_name] = METHOD_GROUPS[group_name] = \
        MethodGroupClass(group_name, _get_class_methods(group_name, methods))
    __all__.append(group_name)
